"use client";
/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */
import { useEffect } from "react";
import { mergeRegister } from "@lexical/utils";
import type { LexicalEditor } from "lexical";
import {
    $createRangeSelection,
    $getSelection,
    $isNodeSelection,
    $setSelection,
    COMMAND_PRIORITY_EDITOR,
    COMMAND_PRIORITY_HIGH,
    COMMAND_PRIORITY_LOW,
    DRAGOVER_COMMAND,
    DRAGSTART_COMMAND,
    DROP_COMMAND
} from "lexical";
import { $isImageNode, ImageNode } from "@webiny/lexical-nodes";
import type { ImagePayload } from "~/commands/index.js";
import { INSERT_IMAGE_COMMAND } from "~/commands/index.js";
import { CAN_USE_DOM } from "~/utils/canUseDOM.js";
import { insertImage } from "~/utils/insertImage.js";
import { useRichTextEditor } from "~/hooks/index.js";

export type InsertImagePayload = Readonly<ImagePayload>;

const getDOMSelection = (targetWindow: Window | null): Selection | null =>
    CAN_USE_DOM ? (targetWindow || window).getSelection() : null;

export function ImagesPlugin({
    captionsEnabled
}: {
    captionsEnabled?: boolean;
}): JSX.Element | null {
    const { editor } = useRichTextEditor();

    useEffect(() => {
        if (!editor.hasNodes([ImageNode])) {
            throw new Error("ImagesPlugin: ImageNode not registered in the editor!");
        }

        return mergeRegister(
            editor.registerCommand<InsertImagePayload>(
                INSERT_IMAGE_COMMAND,
                payload => insertImage(payload),
                COMMAND_PRIORITY_EDITOR
            ),
            editor.registerCommand<DragEvent>(
                DRAGSTART_COMMAND,
                event => {
                    return onDragStart(event);
                },
                COMMAND_PRIORITY_HIGH
            ),
            editor.registerCommand<DragEvent>(
                DRAGOVER_COMMAND,
                event => {
                    return onDragover(event);
                },
                COMMAND_PRIORITY_LOW
            ),
            editor.registerCommand<DragEvent>(
                DROP_COMMAND,
                event => {
                    return onDrop(event, editor);
                },
                COMMAND_PRIORITY_HIGH
            )
        );
    }, [captionsEnabled, editor]);

    return null;
}

function getDragImage() {
    if (!document) {
        return undefined;
    }

    const TRANSPARENT_IMAGE =
        "";
    const img = document.createElement("img");
    img.src = TRANSPARENT_IMAGE;
    return img;
}

function onDragStart(event: DragEvent): boolean {
    const node = getImageNodeInSelection();
    if (!node) {
        return false;
    }
    const dataTransfer = event.dataTransfer;
    const dragImage = getDragImage();
    if (!dataTransfer || !dragImage) {
        return false;
    }
    dataTransfer.setData("text/plain", "_");
    dataTransfer.setDragImage(dragImage, 0, 0);
    dataTransfer.setData(
        "application/x-lexical-drag",
        JSON.stringify({
            data: {
                id: node.__id,
                altText: node.__altText,
                caption: node.__caption,
                height: node.__height,
                key: node.getKey(),
                maxWidth: node.__maxWidth,
                showCaption: node.__showCaption,
                src: node.__src,
                width: node.__width
            },
            type: "image"
        })
    );

    return true;
}

function onDragover(event: DragEvent): boolean {
    const node = getImageNodeInSelection();
    if (!node) {
        return false;
    }
    if (!canDropImage(event)) {
        event.preventDefault();
    }
    return true;
}

function onDrop(event: DragEvent, editor: LexicalEditor): boolean {
    const node = getImageNodeInSelection();
    if (!node) {
        return false;
    }
    const data = getDragImageData(event);
    if (!data) {
        return false;
    }
    event.preventDefault();
    if (canDropImage(event)) {
        const range = getDragSelection(event);
        node.remove();
        const rangeSelection = $createRangeSelection();
        if (range !== null && range !== undefined) {
            rangeSelection.applyDOMRange(range);
        }
        $setSelection(rangeSelection);
        editor.dispatchCommand(INSERT_IMAGE_COMMAND, data);
    }
    return true;
}

function getImageNodeInSelection(): ImageNode | null {
    const selection = $getSelection();
    if (!$isNodeSelection(selection)) {
        return null;
    }
    const nodes = selection.getNodes();
    const node = nodes[0];
    return $isImageNode(node) ? node : null;
}

function getDragImageData(event: DragEvent): null | InsertImagePayload {
    const dragData = event.dataTransfer?.getData("application/x-lexical-drag");
    if (!dragData) {
        return null;
    }
    const { type, data } = JSON.parse(dragData);
    if (type !== "image") {
        return null;
    }

    return data;
}

declare global {
    interface DragEvent {
        rangeOffset?: number;
        rangeParent?: Node;
    }
}

function canDropImage(event: DragEvent): boolean {
    const target = event.target;
    return !!(
        target &&
        target instanceof HTMLElement &&
        !target.closest("code, span.editor-image") &&
        target.parentElement &&
        target.parentElement.closest("div.ContentEditable__root")
    );
}

function getDragSelection(event: DragEvent): Range | null | undefined {
    let range;
    const target = event.target as null | Element | Document;
    const targetWindow =
        target == null
            ? null
            : target.nodeType === 9
              ? (target as Document).defaultView
              : (target as Element).ownerDocument.defaultView;
    const domSelection = getDOMSelection(targetWindow);
    if (document.caretRangeFromPoint) {
        range = document.caretRangeFromPoint(event.clientX, event.clientY);
    } else if (event.rangeParent && domSelection !== null) {
        domSelection.collapse(event.rangeParent, event.rangeOffset || 0);
        range = domSelection.getRangeAt(0);
    } else {
        throw Error(`Cannot get the selection when dragging`);
    }

    return range;
}
